package com.example.lotterysystem.service.impl;

import com.example.lotterysystem.common.errorcode.ServiceErrorCodeConstants;
import com.example.lotterysystem.common.exception.ServiceException;
import com.example.lotterysystem.common.utils.JacksonUtil;
import com.example.lotterysystem.common.utils.RedisUtil;
import com.example.lotterysystem.controller.param.DrawPrizeParam;
import com.example.lotterysystem.controller.param.ShowWinningRecordsParam;
import com.example.lotterysystem.dao.dataobject.*;
import com.example.lotterysystem.dao.mapper.*;
import com.example.lotterysystem.service.DrawPrizeService;
import com.example.lotterysystem.service.dto.WinnerRecordDTO;
import com.example.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.example.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.example.lotterysystem.service.enums.ActivityStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

import static com.example.lotterysystem.common.config.DirectRabbitConfig.EXCHANGE_NAME;
import static com.example.lotterysystem.common.config.DirectRabbitConfig.ROUTING;

@Service
public class DrawPrizeServiceImpl implements DrawPrizeService {

    private static final Logger logger = LoggerFactory.getLogger(DrawPrizeParam.class);

    private final Long WINNING_RECORD_TIMEOUT = 60 * 60 * 24 * 2l;
    private final String WINNING_RECORD_PREFIX = "WINNING_RECORD_";

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private ActivityMapper activityMapper;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private WinningRecordMapper winningRecordMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private PrizeMapper prizeMapper;

    @Autowired
    private RedisUtil redisUtil;

    @Override
    public void drawPrize(DrawPrizeParam param) {

        Map<String, String> map = new HashMap<>();
        map.put("messageId", String.valueOf(UUID.randomUUID()));
        map.put("messageData", JacksonUtil.writeValueAsString(param));


        //发消息: 交换机, 绑定的key, 消息体
        rabbitTemplate.convertAndSend(EXCHANGE_NAME, ROUTING, map);
        logger.info("mq消息发送成功: map={}", JacksonUtil.writeValueAsString(map));

    }

    @Override
    public Boolean checkDrawPrizeParam(DrawPrizeParam param) {

        ActivityDO activityDO = activityMapper.selectById(param.getActivityId());
        //活动是否存在 可以从 activity_prize 表里查, 原因是保存activity做了本地事务, 保证一致性
        ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectByActivityPrizeId(
                param.getActivityId(), param.getPrizeId());

        //活动或者是否存在
        if(null == activityDO || null == activityPrizeDO) {
//            throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_OR_PRIZE_IS_EMPTY);
            logger.info("校验抽奖请求失败!失败原因:{}",ServiceErrorCodeConstants.ACTIVITY_OR_PRIZE_IS_EMPTY.getMsg());
            return false;
        }

        //活动是否有效
        if(activityDO.getStatus().equalsIgnoreCase(ActivityStatusEnum.COMPLETE.name())) {
//            throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_COMPLETED);
            logger.info("校验抽奖请求失败!失败原因:{}",ServiceErrorCodeConstants.ACTIVITY_COMPLETED.getMsg());
            return false;
        }

        //奖品是否有效
        if(activityPrizeDO.getStatus().equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETE.name())) {
//            throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_COMPLETED);
            logger.info("校验抽奖请求失败!失败原因:{}",ServiceErrorCodeConstants.ACTIVITY_PRIZE_COMPLETED.getMsg());
            return false;
        }

        //中奖者人数是否和设置奖品数量一致
        if(activityPrizeDO.getPrizeAmount() != param.getWinnerList().size()) {
//            throw new ServiceException(ServiceErrorCodeConstants.WINNER_PRIZE_AMOUNT_ERROR);
            logger.info("校验抽奖请求失败!失败原因:{}",ServiceErrorCodeConstants.WINNER_PRIZE_AMOUNT_ERROR.getMsg());
            return false;
        }
        return true;
    }

    @Override
    public List<WinningRecordDO> saveWinnerRecords(DrawPrizeParam param) {

        //查询相关信息:活动, 人员, 奖品, 活动关联奖品表
        ActivityDO activityDO = activityMapper.selectById(param.getActivityId());

        List<Long> userIds = param.getWinnerList()
                .stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .collect(Collectors.toList());
        List<UserDO> userDoList = userMapper.batchSelectByIds(userIds);

        PrizeDO prizeDO = prizeMapper.selectById(param.getPrizeId());

        ActivityPrizeDO activityPrizeDO =
                activityPrizeMapper.selectByActivityPrizeId(param.getActivityId(), param.getPrizeId());

        //构造中奖者记录 保存
        List<WinningRecordDO> winningRecordDOList = userDoList.stream()
                .map(userDO -> {
                    WinningRecordDO winningRecordDO = new WinningRecordDO();
                    winningRecordDO.setActivityId(activityDO.getId());
                    winningRecordDO.setActivityName(activityDO.getActivityName());
                    winningRecordDO.setPrizeId(prizeDO.getId());
                    winningRecordDO.setPrizeName(prizeDO.getName());
                    winningRecordDO.setPrizeTier(activityPrizeDO.getPrizeTiers());
                    winningRecordDO.setWinnerId(userDO.getId());
                    winningRecordDO.setWinnerName(userDO.getUserName());
                    winningRecordDO.setWinnerEmail(userDO.getEmail());
                    winningRecordDO.setWinnerPhoneNumber(userDO.getPhoneNumber());
                    winningRecordDO.setWinningTime(param.getWinningTime());
                    return winningRecordDO;
                }).collect(Collectors.toList());
        winningRecordMapper.batchInsert(winningRecordDOList);

        //缓存中奖者记录
        // 1.缓存奖品维度的中奖记录 (WinningRecord_activityId_prizeId_, winningRecordDOList(奖品维度中奖名单))
        cacheWinningRecord(param.getActivityId()+"_"+param.getPrizeId(),
                winningRecordDOList,
                WINNING_RECORD_TIMEOUT);


        // 2.缓存活动维度的中奖记录 (WinningRecord_activityId, winningRecordDOList(活动维度中奖名单))
        // 当活动已完成再存放
        if(activityDO.getStatus().equalsIgnoreCase(ActivityStatusEnum.COMPLETE.name())) {

            //查询活动维度的全量中奖记录
            List<WinningRecordDO> allList = winningRecordMapper.selectByActivityId(param.getActivityId());
            cacheWinningRecord(param.getActivityId()+"",
                    allList,
                    WINNING_RECORD_TIMEOUT);
        }

        return winningRecordDOList;
    }

    @Override
    public void deleteRecords(Long activityId, Long prizeId) {
        if(null == activityId) {
            logger.warn("要删除中奖记录的活动id为空!");
            return;
        }
        //删除数据表
        winningRecordMapper.deleteRecords(activityId, prizeId);

        //删除缓存(奖品维度, 活动维度)
        if(null != prizeId) {
            deleteWinnerRecords(activityId+"_"+prizeId);
        }
        //无论是否传入prizeId, 都需要删除活动维度的中奖记录缓存
        // 如果传递了prizeId, 证明奖品未抽奖, 必须删除活动维度的缓存记录
        // 如果没有传递prizeId, 就只是删除活动维度的信息
        deleteWinnerRecords(activityId+"");
    }

    @Override
    public List<WinnerRecordDTO> getRecords(ShowWinningRecordsParam param) {
        //查询redis
        String key = null == param.getPrizeId()
                ? String.valueOf(param.getActivityId())
                : param.getActivityId() + "_" + param.getPrizeId();
        List<WinningRecordDO> winningRecordDOList = getWinningRecord(key);
        if(!CollectionUtils.isEmpty(winningRecordDOList)) {
            logger.info("从redis中查询到获奖记录:{}", JacksonUtil.writeValueAsString(winningRecordDOList));
            return convertToWinnerRecordDTOList(winningRecordDOList);
        }
        //如果redis不存在, 查库
        winningRecordDOList = winningRecordMapper.selectByActivityIdOrPrizeId(param.getActivityId(), param.getPrizeId());
        logger.info("从数据库中查询到获奖记录:{}", JacksonUtil.writeValueAsString(winningRecordDOList));
        //存放记录到redis
        if(CollectionUtils.isEmpty(winningRecordDOList)) {
            logger.info("查询的中奖记录为空!param{}", JacksonUtil.writeValueAsString(param));
            return Arrays.asList();
        }
        cacheWinningRecord(key, winningRecordDOList, WINNING_RECORD_TIMEOUT);
        return convertToWinnerRecordDTOList(winningRecordDOList);
    }

    private List<WinnerRecordDTO> convertToWinnerRecordDTOList(List<WinningRecordDO> winningRecordDOList) {
        if(CollectionUtils.isEmpty(winningRecordDOList)) {
            return Arrays.asList();
        }
        return winningRecordDOList.stream()
                .map(winningRecordDO -> {
                    WinnerRecordDTO winnerRecordDTO = new WinnerRecordDTO();
                    winnerRecordDTO.setWinnerId(winningRecordDO.getWinnerId());
                    winnerRecordDTO.setWinnerName(winningRecordDO.getWinnerName());
                    winnerRecordDTO.setPrizeName(winningRecordDO.getPrizeName());
                    winnerRecordDTO.setPrizeTier(ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()));
                    winnerRecordDTO.setWinnerTime(winningRecordDO.getWinningTime());
                    return winnerRecordDTO;
                }).collect(Collectors.toList());
    }

    /**
     * 从缓存中删除中奖记录
     */
    private void deleteWinnerRecords(String key) {
        try {
            if(redisUtil.hasKey(WINNING_RECORD_PREFIX + key)) {
                //存在再删除
                redisUtil.del(WINNING_RECORD_PREFIX + key);
            }
        }catch (Exception e) {
            logger.error("删除中奖记录缓存异常, key:{}", key);
        }
    }

    /**
     * 缓存中奖记录
     * @param key
     * @param winningRecordDOList
     * @param time
     */
    private void cacheWinningRecord(String key,
                                    List<WinningRecordDO> winningRecordDOList,
                                    Long time) {
        String str = "";
        try {
            if(!StringUtils.hasText(key )|| CollectionUtils.isEmpty(winningRecordDOList)) {
                logger.warn("要缓存的内容为空! key:{} , value:{}",WINNING_RECORD_PREFIX+key , str);
                return;
            }

            str = JacksonUtil.writeValueAsString(winningRecordDOList);
            redisUtil.set(WINNING_RECORD_PREFIX+key,str, time);
        }catch (Exception e) {
            logger.error("缓存中奖记录异常! key:{} , value:{}",WINNING_RECORD_PREFIX+key , str);
        }
    }

    /**
     * 从缓存中获取中奖记录
     * @param key
     * @return
     */
    private List<WinningRecordDO> getWinningRecord(String key) {

        try {
            if(!StringUtils.hasText(key)) {
                logger.warn("要从缓存中查询的中奖记录key为空!");
                return Arrays.asList();
            }

            String str = redisUtil.get(WINNING_RECORD_PREFIX + key);
            if(!StringUtils.hasText(str)) {
                return Arrays.asList();
            }
            List<WinningRecordDO> winningRecordDOList = JacksonUtil.readListValue(str, WinningRecordDO.class);
            return winningRecordDOList;
        }catch (Exception e) {
            logger.error("从缓存中查询奖记录异常! key:{}",WINNING_RECORD_PREFIX+key);
            return Arrays.asList();
        }

    }
}
